package logic.gridSimInterface.brokers;


import eduni.simjava.Sim_event;
import gridsim.Accumulator;
import gridsim.GridSim;
import gridsim.GridSimTags;
import gridsim.Gridlet;
import gridsim.ResourceCharacteristics;
import gridsim.net.SimpleLink;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Vector;
import logic.JSimulation;
import logic.SimulationParameters;
import logic.Statistic;
import logic.TimeAccumulator;
import logic.gridSimInterface.SimTags;
import logic.gridSimInterface.resource.ResourceCharacteristicExt;
import logic.gridSimInterface.user.GridletInfo;
import logic.schedulers.AlgorithmCollection;
import logic.schedulers.SchedulingHelp;
import logicTests.Dbg;

public class BrokerLocal extends AbstractBroker {
    private int optimizationUsed;
    
    private Vector<Integer> resourcesID;
    private Collection<ResourceCharacteristicExt> resourcesInfo = null;
    private LinkedList<GridletInfo> userGridletInfoLists = new LinkedList<GridletInfo>();
    private LinkedList<GridletInfo> finishedGridletInfos;
    private HashMap<Integer, TimeAccumulator> dlzkyFrontov = null;
    private HashMap<Integer, TimeAccumulator> vyuzitieCPU = null;

    public BrokerLocal(String name, SimpleLink link, String owner) throws Exception {
        super(name, link, owner);
        resourcesID = new Vector<Integer>();
        finishedGridletInfos = new LinkedList<GridletInfo>();
        optimizationUsed = 0;
    }

    protected ResourceCharacteristicExt getResource(int resourceID) {
        for (ResourceCharacteristicExt res : resourcesInfo)
            if (res.getResourceID() == resourceID)
                return res;
        return null;
    }
    
    protected Vector<Integer> getResourcesID() {
        return resourcesID;
    }

    public void addResource(int resourceID) {
        resourcesID.add(resourceID);
    }

    private void setDlzkuFrontu(int resourceID) {
        ResourceCharacteristicExt res = getResource(resourceID);
        dlzkyFrontov.get(resourceID).add(
                GridSim.clock(),
                res.getScheduledGridlets().size()
                );
    }
    
    private void setVyuzitieCPU(int resourceID) {
        ResourceCharacteristicExt res = getResource(resourceID);
        vyuzitieCPU.get(resourceID).add(
                GridSim.clock(),
                res.getUsedCPUcount()/res.getNumPE()*100
                );
    }
    
    /**
     * Pre dany gridlet najde vyhovujuci zdroj. Ak taky zdroj neexistuje,
     * vrati null.
     */
    protected ResourceCharacteristicExt findResourceForGridlet(GridletInfo g) {
    	Collection<ResourceCharacteristicExt> freeResources = 
                SchedulingHelp.getFreeResources(resourcesInfo);
        for (ResourceCharacteristicExt element : freeResources) {
            if(element.isSuitableResource(g))
                return element;
        }
        return null;
    }

    public void body() {
        Sim_event ev = new Sim_event();
        int myResourcesCount = resourcesID.size();
        
        ////////////////////////////////////////////////////////////////////////
        //CAKANIE NA SPRAVU "SIMULATION_START"
        waitForStart();
        Dbg.vypis("Broker " + get_name() + 
                  " recieved start at " + GridSim.clock());

        ////////////////////////////////////////////////////////////////////////
        //NAPLNENIE INFORMACII O ZDROJOCH
        resourcesInfo = getResourcesInfo(resourcesID);
        Dbg.vypis("Entity " + this.get_name() + " recieved complete" +
                " grid resource list (size = " + myResourcesCount + ")" +
                " at time " + GridSim.clock());
        sendReadyToOwner(resourcesID);
        dlzkyFrontov = new HashMap<Integer, TimeAccumulator>(resourcesID.size());
        vyuzitieCPU = new HashMap<Integer, TimeAccumulator>(resourcesID.size());
        for (Integer resourceID : resourcesID) {
            dlzkyFrontov.put(new Integer(resourceID), new TimeAccumulator(GridSim.clock()));
            vyuzitieCPU.put(new Integer(resourceID), new TimeAccumulator(GridSim.clock()));
        }

        ////////////////////////////////////////////////////////////////////////
        //OBSLUHA PRICHADZAJUCICH UDALOSTI
        boolean end = false;
        
        while (!end)
        {
            sim_get_next(ev);
            GridletInfo gridletInfo = null;
            switch(ev.get_tag()) {        	
                case SimTags.GRIDLETINFO_SUBMIT: 
                    if(SimulationParameters.getPlanningType() == SimTags.STATIC_PLANNING) {
                        for (GridletInfo element : (LinkedList<GridletInfo>)ev.get_data()) {
                                element.setBrokerId(this.get_id());
                                userGridletInfoLists.add(element);						
                        }
                        System.out.println("Entity " + get_name() + " recieved gridletInfoList size = " + ((LinkedList<GridletInfo>)ev.get_data()).size());
                        staticPlanning();
                    } else {    //DYNAMIC_PALANNING
                        gridletInfo = (GridletInfo)ev.get_data();
                        gridletInfo.setBrokerId(this.get_id());
                        boolean hasResource = 
                                SchedulingHelp.hasSuitableResource(
                                    resourcesInfo, gridletInfo);
                        if(hasResource)
                            processGridletInfo(gridletInfo, true);
                        else
                            sendUnableToProcessMessage(gridletInfo);
                    }
                    break;    				
                
                case SimTags.GRIDLETINFO_FINISHED:
                    gridletInfo = (GridletInfo)ev.get_data();
                    setDlzkuFrontu(gridletInfo.getResourceId());
                    setVyuzitieCPU(gridletInfo.getResourceId());
                    
                    removeFinishedGridlet(gridletInfo);
                    rescheduleGridlets();
                    finishedGridletInfos.add(gridletInfo);
                    break;
                                                
                case SimTags.RESOURCE_OFFLINE:
                    resourceStatusChanged((Integer)ev.get_data(), false);
                    break;
                case SimTags.RESOURCE_ONLINE:
                    resourceStatusChanged((Integer)ev.get_data(), true);
                    break;
                case SimTags.USER_OFFLINE:
                    userStatusChanged((Integer)ev.get_data(), false);
                    break;
                case SimTags.USER_ONLINE:
                    userStatusChanged((Integer)ev.get_data(), true);
                    break;
                case GridSimTags.END_OF_SIMULATION:
                    end = true; 
                    break;
                default:
                    if (!handleMessage(ev))
                        System.out.println("Warnng: Entity " + get_name() +
                                " recieved unknown message " +
                                ev.get_tag());
            }
        }
        
        ////////////////////////////////////////////////////////////////////////
        //ZAPISANIE VYSLEDNYCH STATISTIK
        for (Integer resID : resourcesID) {
            TimeAccumulator frontTA = dlzkyFrontov.get(resID);
            frontTA.add(GridSim.clock(), 0);
            JSimulation.addStatistic(
                    new Statistic(Statistic.FRONT_LENGTH,
                                  GridSim.getEntityName(resID),
                                  frontTA.getMean())
                    );
            TimeAccumulator vyuziteTA = vyuzitieCPU.get(resID);
            vyuziteTA.add(GridSim.clock(), 0);
            JSimulation.addStatistic(
                    new Statistic(Statistic.CPU_USAGE,
                                  GridSim.getEntityName(resID),
                                  vyuziteTA.getMean())
                    );
        }
        
        JSimulation.addStatistic(
                new Statistic(Statistic.OPTIMIZATION_USED,
                              get_name(),
                              optimizationUsed)
                );
        
        super.terminateIOEntities();
    }

    /**
     * Spustenie optimalizacie rozvrhov zdrojov.
     */
    protected void optimizeSchedule() {
        if (SimulationParameters.getPlanningType() == SimTags.DYNAMIC_PLANNING) {
            if(getAllScheduledGridletinfosCount() > 0 && hasPossibleMoves()) { //here can be specified when it is good to use optimalization
                optimizationUsed++;
                SimulationParameters.getLocalScheduler().optimizeDynamic(
                    SimulationParameters.getCostFunction(),
                    resourcesInfo
                    );
            };
        } else {
                optimizationUsed++;
                SimulationParameters.getLocalScheduler().optimizeStatic(
                    SimulationParameters.getCostFunction(),
                    resourcesInfo,
                    userGridletInfoLists
                    );
        }
    }

    protected void staticPlanning() {  
        if (userGridletInfoLists.size() > 0) {
            Collections.sort(userGridletInfoLists, new CompareGridletAttribute(SimTags.COMPARE_BY_TIME));
            optimizeSchedule();
            rescheduleGridlets();
        }
    }
 
    ////////////////////////////////////////////////////////////////////////////
    //////////////////////// PREVZATE METODY ///////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////
 
    /**
     * Function responsible for work with gridletInfo that just arrived, this grigridletInfo
     * is either immediately send to resource to be processed if there are any free PE on resources, 
     * or is added to schedule of one resource. After that optimalization can be run to optimalize schedules.  
     * @param gridletInfo to process
     * @param doOptimization if true, then optimalize()
     */
    protected void processGridletInfo(GridletInfo gridletInfo, boolean doOptimization) {
    	ResourceCharacteristicExt bestFree = findResourceForGridlet(gridletInfo);
    	if(bestFree != null) {    		
            gridletInfo.setResourceId(bestFree.getResourceID());
            setVyuzitieCPU(bestFree.getResourceID());
            super.send(gridletInfo.getUserId(), GridSimTags.SCHEDULE_NOW, SimTags.GRIDLETINFO_RETURN, gridletInfo);
            bestFree.addGridletInfoToProceesedList(gridletInfo);
            System.out.println("Entity " + super.get_name() + " added gridletInfo about gridlet with ID " + gridletInfo.getGridletId() + " from " + super.getEntityName(gridletInfo.getUserId()) + " to " + super.getEntityName(gridletInfo.getResourceId()) + " at time " + GridSim.clock());
    	}else {
            double bestFinishTime = Double.MAX_VALUE;
            ResourceCharacteristicExt bestResource = resourcesInfo.iterator().next();
            for (ResourceCharacteristicExt element : resourcesInfo) {
                double finishTime = element.calculateFinishTime(gridletInfo);
                if(finishTime < bestFinishTime && element.isSuitableResource(gridletInfo))  {
                        bestResource = element; 
                        bestFinishTime = finishTime;
                }
            }
            setDlzkuFrontu(bestResource.getResourceID());
            bestResource.addGridletInfoToScheduleList(gridletInfo);
            System.out.println("Entity " + super.get_name() + " scheduled gridletInfo about gridlet with ID " + gridletInfo.getGridletId() + " to " + super.getEntityName(gridletInfo.getResourceId()) + " at time " + GridSim.clock());
            if(doOptimization) { 
                    optimizeSchedule();
            }
    	}    		
    }

    /**
     * Return true if gridletInfos predecessor is among finished gridlet
     * @param gridletInfo whose predecessor are we seraching for
     * @return true if predecessor is finished, false otherwise
     */
    protected boolean hasFinishedPredecessor(GridletInfo gridletInfo) {
    	if(gridletInfo.getPredecessorId() == -1)
    		return true;
        
        for (GridletInfo element : finishedGridletInfos) {
            if(element.getUserId() == gridletInfo.getUserId()
               && element.getGridletId() == gridletInfo.getPredecessorId())
                return true;
        }
        
    	return false;
    }

    /**
     * Get number of all scheduled gridletInofs from all online resources
     * @return number of all scheduled gridletInofs on online resources
     */
    protected int getAllScheduledGridletinfosCount() {
    	int retVal = 0;
        for (ResourceCharacteristicExt element : resourcesInfo) {
            retVal += element.getScheduledGridlets().size();
        }
    	return retVal;
    }
    
    /**
     * Finds out if any of actualy scheduled gridletInfos on all resources can be moved to other resource
     * @return true if there is possible move, false otherwise
     */
    protected boolean hasPossibleMoves() {
        for (ResourceCharacteristicExt resInfo : resourcesInfo) {
            if(hasPossibleMoves(resInfo))
                return true;
        }
    	return false;
    }
   
    /**
     * Finds out if any of actualy scheduled gridletInfos from resourceInfo can be moved to other resource
     * @param resourceInfo from where the gridletInfos are taken 
     * @return true if there is possible move, false otherwise
     */
    protected boolean hasPossibleMoves(ResourceCharacteristicExt resourceInfo) {
        for (GridletInfo gi: resourceInfo.getScheduledGridlets()) {
            if (hasPossibleMoves(gi))
                return true;
        }
        return false;
    }
    
    /**
     * Finds out if given gridletInfo can be processed on more then one resource 
     * @param gridletInfo to place
     * @return true if there are more then one possible placement for gridletInfo, otherwise false
     */
    protected boolean hasPossibleMoves(GridletInfo gridletInfo) {
    	int suitableResources = 0;
        for (ResourceCharacteristicExt resourceInfo : resourcesInfo) {
            if(resourceInfo.isSuitableResource(gridletInfo))
                suitableResources++;
        }
        return (suitableResources > 1);
    }
    
    /**
     * Comparator that sorts GridletInfo objects according to their length or creation time. 
     * GridletInfo with greatest length or smallest creation time will be first.
     * @author Miroslav Bellak
     *
     */
    private class CompareGridletAttribute implements Comparator<GridletInfo> {
    	private int compareBy = SimTags.COMPARE_BY_TIME;
        public CompareGridletAttribute(int compareBy) {
            super();
            this.compareBy = compareBy;
        }
        
        public int compare(GridletInfo gridletInfo1, GridletInfo gridletInfo2) {  
        	Double gi1 = null, gi2 = null;
            switch (compareBy) {
			case SimTags.COMPARE_BY_TIME:
	            gi1 = new Double(gridletInfo1.getCreationTime());
	            gi2 = new Double(gridletInfo2.getCreationTime());
				return gi1.compareTo(gi2);
			case SimTags.COMPARE_BY_LENGTH:
	            gi1 = new Double(gridletInfo1.getLength());
	            gi2 = new Double(gridletInfo2.getLength());
				return gi2.compareTo(gi1);
			default:
	            gi1 = new Double(gridletInfo1.getCreationTime());
				gi2 = new Double(gridletInfo2.getCreationTime());
				return gi1.compareTo(gi2);				
			}
            
        }        
    }
    
    /**
     * Finds gridletInfo in resource schedule list from broker resourceInfoList and removes it 
     * @param gridletInfo to remove
     */
    protected void removeFinishedGridlet(GridletInfo gridletInfo) {
        for (ResourceCharacteristicExt element : resourcesInfo) {
            if(element.getResourceID() == gridletInfo.getResourceId()) {
                element.removeGridletInfoFromProcessedList(gridletInfo);
                break;
            }				
        }
    }

    /**
     * This function take gridlets(if there are any) from scheduledGridlets list and 
     * add them to free PE(if there are any) on the same resource 
     */
    protected void rescheduleGridlets() {
        for (ResourceCharacteristicExt element : resourcesInfo) {
            for (GridletInfo gridletInfo : element.rescheduleGrdiletInfos()) {
                gridletInfo.setResourceId(element.getResourceID());
                double delayTime = GridSimTags.SCHEDULE_NOW;
                if (gridletInfo.getCreationTime() > GridSim.clock())
                    delayTime = gridletInfo.getCreationTime() - GridSim.clock();
                super.send(gridletInfo.getUserId(), delayTime, SimTags.GRIDLETINFO_RETURN, gridletInfo);	    		
                System.out.println("Entity " + super.get_name() + " added gridletInfo about gridlet with ID " + gridletInfo.getGridletId() + " to " + super.getEntityName(gridletInfo.getResourceId()) + " at time " + GridSim.clock() + delayTime);
            }
    	}
    }    
    
}
